﻿using StackExchange.Redis;
using System.Collections.Concurrent;

namespace XYS.Service.Core.Service
{
    /// <summary>
    /// 
    /// </summary>
    public class LockService
    {
        private readonly ICache cacheService;
        /// <summary>
        /// 
        /// </summary>
        public LockService(ICache cacheService)
        {
            this.cacheService = cacheService;
        }

        private ConcurrentDictionary<string, LockInstance> LockInstanceMap = new();

        /// <summary>
        /// 创建锁
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public LockInstance CreateLockService(string key)
        {
            if (!LockInstanceMap.TryGetValue(key, out var instance))
            {
                instance = new LockInstance(key, cacheService);
                LockInstanceMap[key] = instance;
            }
            return instance;
        }
    }


    /// <summary>
    /// 锁 实现实例
    /// </summary>
    public class LockInstance : IDisposable
    {
        private readonly ICache cacheService;
        private readonly string cacheKey;
        private string cacheLockValue;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="cacheService"></param>
        public LockInstance(string key, ICache cacheService)
        {
            cacheKey = key;
            this.cacheService = cacheService;
        }

        /// <summary>
        /// 获取一个锁
        /// </summary>
        /// <param name="taskTimeout">获取锁最多使用的时间（超过这个时间还没获取到则直接返回false）</param>
        /// <param name="validTime">持有这个锁的时间（如果超过这个时间，锁会自动释放）</param>
        /// <returns></returns>
        public bool Take(TimeSpan taskTimeout, TimeSpan? validTime = null)
        {
            validTime ??= TimeSpan.FromMinutes(3);
            var expireTime = DateTime.Now.Add(taskTimeout);
            var uniqueValue = Guid.NewGuid().ToString();
            while (DateTime.Now < expireTime)
            {
                if (cacheService.RedisDB.StringSet(cacheKey, uniqueValue, validTime, When.NotExists))
                {
                    cacheLockValue = uniqueValue;
                    return true;
                }
                Thread.Sleep(1);
            }
            return false;
        }


        /// <summary>
        /// 获取一个锁
        /// </summary>
        /// <param name="taskTimeout">获取锁最多使用的时间（超过这个时间还没获取到则直接返回false）</param>
        /// <param name="validTime">持有这个锁的时间（如果超过这个时间，锁会自动释放）</param>
        /// <returns></returns>
        public async Task<bool> TakeAsync(TimeSpan taskTimeout, TimeSpan? validTime = null)
        {
            validTime ??= TimeSpan.FromMinutes(3);
            var expireTime = DateTime.Now.Add(taskTimeout);
            var uniqueValue = Guid.NewGuid().ToString();
            while (DateTime.Now < expireTime)
            {
                if (await cacheService.RedisDB.StringSetAsync(cacheKey, uniqueValue, validTime, When.NotExists))
                {
                    cacheLockValue = uniqueValue;
                    return true;
                }
                await Task.Delay(TimeSpan.FromMilliseconds(1));
            }
            return false;
        }

        /// <summary>
        /// 以默认时间 获取一个锁
        /// 超过30秒还没获取到则直接返回false
        /// </summary>
        public Task<bool> AcquireAsync => TakeAsync(TimeSpan.FromSeconds(30));

        /// <summary>
        /// 以默认时间 获取一个锁
        /// 超过30秒还没获取到则直接返回false
        /// </summary>
        public bool Acquire => Take(TimeSpan.FromSeconds(30));

        /// <summary>
        /// 
        /// </summary>
        public void Dispose()
        {
            string value = cacheService.RedisDB.StringGet(cacheKey);
            if (value == cacheLockValue) cacheService.Remove(cacheKey);
        }
    }
}
